#include <stdlib.h>
#include <strings.h>
#include  <netinet/in.h>
#include <stdio.h>

#include "type_announce.h"
#include "type_routingtable.h"
#include "multicast_routing_table.h"
#include "util.h"
#include "typecast_timeout.h"
#include "typecast_socket.h"
#include "aodv_rreq.h"
#include "maodv_mact.h"

// Type Announce sequence number

static long seqno = 0; 


int GenerateTypeAnnouncePacket(char* buf)
{
    char * tail = NULL;                                     // Tail of the packet
    mrt_table_t * mrt; 
    mrt_nexthop_t * nexthop;     
    
    static int first_time = 1;
    // Initialize seqno to a random number on the first packet.
    if (first_time == 1)
    {
        first_time = 0; 
        seqno = random(); 
    }

    // Fill the packet header
    ta_header_t * packet_header = (ta_header_t*) buf;       // Packet first contains ta_header_t
    tail = buf + sizeof(ta_header_t);

    // Populate header fields
    packet_header->seq_no = htonl(seqno); 
    seqno++; 
    packet_header->multicast_group_count = 0;               // Initialize to 0, will change at the end

    tail = buf + sizeof(ta_header_t);                       // Move tail

//    printf("  Generating TA packet from %s, seq_no = %ld.\n", ip_to_string(get_local_ip_address()), seqno);

    // Fill in the rest of the packet now with the group and neighbor information.
    ta_group_entry_t * multicast_group_entry = NULL;        // Entry of multicast group to fill 
    ta_neighbor_entry_t * neighbor_entry = NULL;            // Entry of neighbors information

    // Iteratively add the groups into the buffer.
    multicast_group_node* mglIter = NULL;
    for (mglIter = GetRoutingTable();
        mglIter != NULL;
        mglIter = mglIter->next)
    {
        mrt = mrt_table_find(mglIter->multicast_group_addr); 
        // only add this group if we are either the member or router
        if (mrt == NULL || ! ( mrt->am_leader || mrt->is_member || mrt->is_router)) { 
            continue; 
        }    
        
        multicast_group_entry = (ta_group_entry_t*) tail;     // Add this group to the packet buffer

        // Populate the group information
        multicast_group_entry->multicast_group_addr = htonl(mglIter->multicast_group_addr); 
        multicast_group_entry->neighbors_count = 0;

        tail += sizeof(ta_group_entry_t);                     // Move tail

//        printf("    Multicast group: grp_addr = %s.\n", ip_to_string(mglIter->multicast_group_addr));

        // Now iteratively add the neighbors that belong in this group.
        multicast_neighbor_node * nbrIter = NULL;
        for (nbrIter = mglIter->neighborHead;
            nbrIter != NULL;
            nbrIter = nbrIter->next)
        {

            nexthop = mrt_nexthop_find(mrt, nbrIter->multicast_neighbor_addr);               
            // If the neighbor is not on the multicast tree anymore, just drop it. 
            if (nexthop == NULL || !nexthop->activated) { 
                continue; 
            }
            neighbor_entry = (ta_neighbor_entry_t*) tail;

            // Populate the neighbor information
            neighbor_entry->addr = htonl(nbrIter->multicast_neighbor_addr);
            copy_bloom_filter(neighbor_entry->ABF, nbrIter->ABF); 

            multicast_group_entry->neighbors_count++; 
            tail += sizeof(ta_neighbor_entry_t);               // Move tail

//            printf("      Neighbor: neighbor_addr = %s, ABF = %s.\n", ip_to_string(neighbor_entry->addr), bloom_filter_to_string(neighbor_entry->ABF));
        }

        packet_header->multicast_group_count++; 
    }
    struct timeval now;     
    gettimeofday(&now, NULL);
    printf("====Sending Type Announce Packet at %s", ctime(&now.tv_sec));     
    DumpTypeAnnouncePacket(buf, stdout);    
    return tail - buf; 
}

void ProcessTypeAnnouncePacket(char* buf, u_int32_t srcAddr)
{
    struct timeval now;     
    gettimeofday(&now, NULL);
    printf("====Received Type Announce Packet from %s at %s\n", ip_to_string(srcAddr), ctime(&now.tv_sec));     
    DumpTypeAnnouncePacket(buf, stdout); 

    ta_header_t* packet_header = (ta_header_t*) buf;
    long seq_no = ntohl(packet_header->seq_no); 
    char * tail = buf + sizeof(ta_header_t);
    u_int32_t grp_addr; 
    u_int32_t neighbor_addr; 
    bloom_filter_t filter; 
    mrt_table_t * mrt; 
    mrt_nexthop_t * nexthop;     
    multicast_neighbor_node* typecast_neighbor; 
 //   printf("  Processing TA packet from %s, seq_no = %ld.\n", ip_to_string(srcAddr), seq_no);
 //   printf("    Local IP address = %s.\n", ip_to_string(get_local_ip_address()));

    // Traverse the packet 
    int i = 0;
    int j; 
    int routing_table_changed = 0; 
    int find_abf_to_me = 0; 
    for (i = 0; i < packet_header->multicast_group_count; i++)
    {
        // Iterate through the contents of the packet.  One multicast group at a time:
        ta_group_entry_t * currentGroup = (ta_group_entry_t*) tail;
        tail += sizeof(ta_group_entry_t);       // Now tail points to the first neighbor of this group.
        grp_addr = ntohl(currentGroup->multicast_group_addr);
        
        find_abf_to_me = 0;  

        for (j = 0; j < currentGroup->neighbors_count; j++)
        {
            ta_neighbor_entry_t * currentNeighbor = (ta_neighbor_entry_t*) tail;
            tail += sizeof(ta_neighbor_entry_t);
            neighbor_addr = ntohl(currentNeighbor->addr);

 //           printf("      Neighbor %d: neighbor_addr = %s.\n", j, ip_to_string(neighbor_addr));

            // This node is addressed to us.  We need to pick up this ABF.
            if (neighbor_addr == get_local_ip_address())
            {
                find_abf_to_me = 1; 
                mrt = mrt_table_find(grp_addr); 
                // Should we check whether we are the member here also ???
                if (mrt == NULL || !mrt->is_router) { 
                    printf("!!! Receieved unexpected TA entry for group %s from node %s, ignore it\n", ip_to_string(grp_addr), ip_to_string(srcAddr)); 
                    continue; 
                }

                // Validate that we have this group, otherwise just move on.
                if (FindMulticastGroup(grp_addr) == NULL)
                {
                    // We are the router, but no LBF yet, insert an empty LBF
                    init_bloom_filter(filter);                     
                    InsertMulticastGroup(grp_addr, filter);  
                    routing_table_changed = 1; 
                }

//                printf("        Inserting m %s and n %s with ABF %s.\n", 
//                                ip_to_string(grp_addr), ip_to_string(srcAddr), bloom_filter_to_string(filter));                                
                nexthop = mrt_nexthop_find(mrt, srcAddr);               
                if (nexthop != NULL && nexthop->activated) {  
                     typecast_neighbor = FindMulticastNeighbor(grp_addr, srcAddr);    
                     if (typecast_neighbor == NULL) {                        
                        InsertMulticastNeighbor(grp_addr, srcAddr, currentNeighbor->ABF); 
                        routing_table_changed = 1; 
                     } else {
                        if (UpdateMulticastNeighbor(typecast_neighbor, currentNeighbor->ABF)) {                                                 
                                routing_table_changed = 1; 
                        }
                     }
                } else { 
                    printf("!!! Receieved inactive TA entry for group %s from node %s, ignore it\n", ip_to_string(grp_addr), ip_to_string(srcAddr)); 
                }
                   
            }
        }
        
        if (!find_abf_to_me) {   
            // reinitate the group discovery process if the TA comes from upstream node. 
            mrt = mrt_table_find(grp_addr); 
            if (mrt != NULL && ( mrt->is_router || mrt->is_member  ) && !mrt->is_broken && 
                    !mrt->am_leader && !mrt->in_repair && (mrt->activated_upstream_cnt >0 )) { 
                mrt_nexthop_t * upstream = mrt_table_get_activated_upstream(mrt);             
                if (upstream->activated && (srcAddr == upstream->addr)) { 
                    // upstream did not send us any TA update, the tree is broken. send an unsolicited MACT 
                    printf("@@@@@@@  Upstream node does not update TA to me, tree is broken! Send MACT\n"); 
                    mact_send(MACT_JOIN, mrt->leader_hcnt, grp_addr, srcAddr, 1); 
                    
                    /*
                    mrt->is_broken = 1;
                    mrt_nexthop_deactivate(mrt, srcAddr);
                    // we lost our upstream, find a new one 
                    rreq_route_discovery(mrt->grp_addr, 
                        RREQ_JOIN|RREQ_GRP_REBUILD, NULL, 0);
                    */

                }
            }
            
        } 
    }
    if (routing_table_changed) { 
            printf("TypeCast routing table changed\n"); 
            DumpTypeCastRoutingTable(stdout);     
            //@TODO: if this is the first time the entry is updated, we should immediately send a TA packet instead of 
            // waiting for timeout 
            typecast_send_TA(); 
            start_ta_timer();
    } else { 
            printf("TypeCast routing table does not changed\n"); 
            DumpTypeCastRoutingTable(stdout);     
    }       
    
}

void DumpTypeAnnouncePacket(char * buf, int fd)
{ 
    ta_header_t* packet_header = (ta_header_t*) buf;
    long seq_no = ntohl(packet_header->seq_no); 
    FILE * file = (FILE *)fd; 
    char * tail = buf + sizeof(ta_header_t);
    u_int32_t grp_addr; 
    u_int32_t neighbor_addr; 
    bloom_filter_t filter; 
    mrt_table_t * mrt; 
    mrt_nexthop_t * nexthop;     
    multicast_neighbor_node* typecast_neighbor; 
    fprintf(file, "seqno=%ld\n", seq_no);     
    fprintf(file, "num_of_grps=%d\n", (int)packet_header->multicast_group_count);     

    // Traverse the packet 
    int i = 0;
    int j; 
    for (i = 0; i < packet_header->multicast_group_count; i++)
    {
        // Iterate through the contents of the packet.  One multicast group at a time:
        ta_group_entry_t * currentGroup = (ta_group_entry_t*) tail;
        tail += sizeof(ta_group_entry_t);       // Now tail points to the first neighbor of this group.
        grp_addr = ntohl(currentGroup->multicast_group_addr);        
        fprintf(file, "grp_addr = %s,  num_of_neighbors=%d\n", ip_to_string(grp_addr), currentGroup->neighbors_count);
        for (j = 0; j < currentGroup->neighbors_count; j++)
        {
            ta_neighbor_entry_t * currentNeighbor = (ta_neighbor_entry_t*) tail;
            tail += sizeof(ta_neighbor_entry_t);
            neighbor_addr = ntohl(currentNeighbor->addr);
            fprintf(file, "\tneighbor=%s  ABF=%s\n", ip_to_string(neighbor_addr), bloom_filter_to_string(currentNeighbor->ABF));
        }
    }        
    fprintf(file, "total size of the packet = %d\n", (int) (tail - buf)); 
}
